home *** CD-ROM | disk | FTP | other *** search
- // Copyright (C) 2001-2002 Raven Software.
- //
- // cg_snapshot.c -- things that happen on snapshot transition,
- // not necessarily every single rendered frame
-
- #include "cg_local.h"
-
- /*
- ==================
- CG_ResetEntity
- ==================
- */
- static void CG_ResetEntity( centity_t *cent )
- {
- // if the previous snapshot this entity was updated in is at least
- // an event window back in time then we can reset the previous event
- if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC )
- {
- cent->previousEvent = 0;
- }
-
- cent->trailTime = cg.snap->serverTime;
-
- VectorCopy (cent->currentState.origin, cent->lerpOrigin);
- VectorCopy (cent->currentState.angles, cent->lerpAngles);
- if ( cent->currentState.eType == ET_PLAYER )
- {
- CG_ResetPlayerEntity( cent );
- }
-
- cent->pe.weapon = 0;
- }
-
- /*
- ===============
- CG_TransitionEntity
-
- cent->nextState is moved to cent->currentState and events are fired
- ===============
- */
- static void CG_TransitionEntity( centity_t *cent )
- {
- cent->currentState = cent->nextState;
- cent->currentValid = qtrue;
-
- // reset if the entity wasn't in the last frame or was teleported
- if ( !cent->interpolate ) {
- CG_ResetEntity( cent );
- }
-
- // Time is overloaded for players to store the spawn count, so when a player is newly spawned
- // their gore should be reset
- if ( cent->pe.spawnCount != cent->currentState.time )
- {
- cent->pe.spawnCount = cent->currentState.time;
-
- // Force all the animations to be restarted
- cent->pe.torso.anim = -1;
- cent->pe.legs.anim = -1;
-
- if ( cent->ghoul2 )
- {
- trap_G2API_ClearSkinGore ( cent->ghoul2 );
- }
- }
-
- // clear the next state. if will be set by the next CG_SetNextSnap
- cent->interpolate = qfalse;
-
- // check for events
- CG_CheckEvents( cent );
- }
-
-
- /*
- ==================
- CG_SetInitialSnapshot
-
- This will only happen on the very first snapshot, or
- on map restarts. All other times will use
- CG_TransitionSnapshot instead.
-
- FIXME: Also called by map_restart?
- ==================
- */
- void CG_SetInitialSnapshot( snapshot_t *snap )
- {
- int i;
- centity_t *cent;
- entityState_t *state;
-
- cg.snap = snap;
-
- cent = CG_GetEntity ( snap->ps.clientNum );
-
- if (!cgs.clientinfo[snap->ps.clientNum].ghoul2Model)
- {
- Com_Error(ERR_DROP, "CG_SetInitialSnapshot invalid g2 pointer for client\n");
- }
-
- if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[snap->ps.clientNum].ghoul2Model))
- {
- trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[snap->ps.clientNum].ghoul2Model, ¢->ghoul2);
- }
-
- BG_PlayerStateToEntityState( &snap->ps, ¢->currentState, qfalse );
-
- // sort out solid entities
- CG_BuildSolidList();
-
- CG_ExecuteNewServerCommands( snap->serverCommandSequence );
-
- // set our local weapon selection pointer to
- // what the server has indicated the current weapon is
- CG_Respawn();
-
- for ( i = 0 ; i < cg.snap->numEntities ; i++ )
- {
- state = &cg.snap->entities[ i ];
- cent = CG_GetEntity ( state->number );
-
- memcpy(¢->currentState, state, sizeof(entityState_t));
- //cent->currentState = *state;
- cent->interpolate = qfalse;
- cent->currentValid = qtrue;
-
- CG_ResetEntity( cent );
-
- // check for events
- CG_CheckEvents( cent );
- }
- }
-
-
- /*
- ===================
- CG_TransitionSnapshot
-
- The transition point from snap to nextSnap has passed
- ===================
- */
- static void CG_TransitionSnapshot( void )
- {
- centity_t *cent;
- snapshot_t *oldFrame;
- int i;
-
- if ( !cg.snap )
- {
- Com_Error( ERR_FATAL, "CG_TransitionSnapshot: NULL cg.snap" );
- }
-
- if ( !cg.nextSnap )
- {
- Com_Error( ERR_FATAL, "CG_TransitionSnapshot: NULL cg.nextSnap" );
- }
-
- // execute any server string commands before transitioning entities
- CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence );
-
- // if we had a map_restart, set everthing with initial
- if ( !cg.snap )
- {
- }
-
- // clear the currentValid flag for all entities in the existing snapshot
- for ( i = 0 ; i < cg.snap->numEntities ; i++ )
- {
- cent = CG_GetEntity ( cg.snap->entities[ i ].number );
- cent->currentValid = qfalse;
- }
-
- // move nextSnap to snap and do the transitions
- oldFrame = cg.snap;
- cg.snap = cg.nextSnap;
-
- cent = CG_GetEntity ( cg.snap->ps.clientNum );
- BG_PlayerStateToEntityState( &cg.snap->ps, ¢->currentState, qfalse );
- cent->interpolate = qfalse;
- cent->currentValid = qtrue;
-
- for ( i = 0 ; i < cg.snap->numEntities ; i++ )
- {
- // Transition the entity
- cent = CG_GetEntity ( cg.snap->entities[ i ].number );
- CG_TransitionEntity( cent );
-
- // remember time of snapshot this entity was last updated in
- cent->snapShotTime = cg.snap->serverTime;
- }
-
- cg.nextSnap = NULL;
-
- // check for playerstate transition events
- if ( oldFrame )
- {
- playerState_t *ops, *ps;
-
- ops = &oldFrame->ps;
- ps = &cg.snap->ps;
-
- // teleporting checks are irrespective of prediction
- if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT )
- {
- cg.thisFrameTeleport = qtrue; // will be cleared by prediction code
- }
-
- // if we are not doing client side movement prediction for any
- // reason, then the client events and view changes will be issued now
- if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW)
- || cg_nopredict.integer || cg_synchronousClients.integer )
- {
- CG_TransitionPlayerState( ps, ops );
- }
- }
- }
-
- /*
- ===================
- CG_SetNextSnap
-
- A new snapshot has just been read in from the client system.
- ===================
- */
- static void CG_SetNextSnap( snapshot_t *snap )
- {
- int num;
- entityState_t *es;
- centity_t *cent;
-
- cg.nextSnap = snap;
-
- cent = CG_GetEntity ( snap->ps.clientNum );
-
- BG_PlayerStateToEntityState( &snap->ps, ¢->nextState, qfalse );
- cent->interpolate = qtrue;
-
- // check for extrapolation errors
- for ( num = 0 ; num < snap->numEntities ; num++ )
- {
- es = &snap->entities[num];
- cent = CG_GetEntity ( es->number );
-
- memcpy(¢->nextState, es, sizeof(entityState_t));
- //cent->nextState = *es;
-
- // if this frame is a teleport, or the entity wasn't in the
- // previous frame, don't interpolate
- if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) )
- {
- cent->interpolate = qfalse;
- }
- else
- {
- cent->interpolate = qtrue;
- }
- }
-
- // if the next frame is a teleport for the playerstate, we
- // can't interpolate during demos
- if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) )
- {
- cg.nextFrameTeleport = qtrue;
- }
- else
- {
- cg.nextFrameTeleport = qfalse;
- }
-
- // if changing follow mode, don't interpolate
- if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum )
- {
- cg.nextFrameTeleport = qtrue;
- }
-
- // if changing server restarts, don't interpolate
- if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT )
- {
- cg.nextFrameTeleport = qtrue;
- }
-
- // sort out solid entities
- CG_BuildSolidList();
- }
-
-
- /*
- ========================
- CG_ReadNextSnapshot
-
- This is the only place new snapshots are requested
- This may increment cgs.processedSnapshotNum multiple
- times if the client system fails to return a
- valid snapshot.
- ========================
- */
- static snapshot_t *CG_ReadNextSnapshot( void ) {
- qboolean r;
- snapshot_t *dest;
-
- if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) {
- Com_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i",
- cg.latestSnapshotNum, cgs.processedSnapshotNum );
- }
-
- while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) {
-
- /*
- // decide which of the two slots to load it into
- if ( cg.snap == &cg.activeSnapshots[0] ) {
- dest = &cg.activeSnapshots[1];
- } else {
- dest = &cg.activeSnapshots[0];
- }
- */
- dest = &cg.activeSnapshots[cg.activeSnapshot%3];
-
- // try to read the snapshot from the client system
- cgs.processedSnapshotNum++;
- r = trap_GetSnapshot( cgs.processedSnapshotNum, dest );
-
- // FIXME: why would trap_GetSnapshot return a snapshot with the same server time
- if ( cg.nextSnap && r && dest->serverTime == cg.nextSnap->serverTime ) {
- // r = r;
- // continue;
- }
-
- // if it succeeded, return
- if ( r ) {
- cg.activeSnapshot++;
- CG_AddLagometerSnapshotInfo( dest );
- return dest;
- }
-
- // a GetSnapshot will return failure if the snapshot
- // never arrived, or is so old that its entities
- // have been shoved off the end of the circular
- // buffer in the client system.
-
- // record as a dropped packet
- CG_AddLagometerSnapshotInfo( NULL );
-
- // If there are additional snapshots, continue trying to
- // read them.
- }
-
- // nothing left to read
- return NULL;
- }
-
-
- /*
- ============
- CG_ProcessSnapshots
-
- We are trying to set up a renderable view, so determine
- what the simulated time is, and try to get snapshots
- both before and after that time if available.
-
- If we don't have a valid cg.snap after exiting this function,
- then a 3D game view cannot be rendered. This should only happen
- right after the initial connection. After cg.snap has been valid
- once, it will never turn invalid.
-
- Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot
- hasn't arrived yet (it becomes an extrapolating situation instead
- of an interpolating one)
-
- ============
- */
- void CG_ProcessSnapshots( void )
- {
- snapshot_t *snap;
- int n;
-
- // see what the latest snapshot the client system has is
- trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime );
- if ( n != cg.latestSnapshotNum )
- {
- if ( n < cg.latestSnapshotNum )
- {
- // this should never happen
- Com_Error( ERR_FATAL, "CG_ProcessSnapshots: n < cg.latestSnapshotNum" );
- }
- cg.latestSnapshotNum = n;
- }
-
- // If we have yet to receive a snapshot, check for it.
- // Once we have gotten the first snapshot, cg.snap will
- // always have valid data for the rest of the game
- while ( !cg.snap )
- {
- snap = CG_ReadNextSnapshot();
-
- if ( !snap )
- {
- // we can't continue until we get a snapshot
- return;
- }
-
- // set our weapon selection to what
- // the playerstate is currently using
- if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) )
- {
- CG_SetInitialSnapshot( snap );
- }
- }
-
- // loop until we either have a valid nextSnap with a serverTime
- // greater than cg.time to interpolate towards, or we run
- // out of available snapshots
- do
- {
- // if we don't have a nextframe, try and read a new one in
- if ( !cg.nextSnap || cg.needNextSnap ) // !cg.nextSnap )
- {
- snap = CG_ReadNextSnapshot();
-
- // if we still don't have a nextframe, we will just have to
- // extrapolate
- if ( !snap )
- {
- if ( cg_drawSnapshot.integer )
- Com_Printf ( "NOSNAP\n" );
-
-
- break;
- }
-
- #ifdef _SNAPSHOT_EXTRAPOLATION
-
- cg.needNextSnap = qfalse;
-
- if ( cg.nextSnap)
- {
- if ( cg_drawSnapshot.integer )
- Com_Printf ( "TRANS\n" );
-
- CG_TransitionSnapshot();
- }
- #endif
-
- CG_SetNextSnap( snap );
-
-
- // if time went backwards, we have a level restart
- if ( cg.nextSnap->serverTime < cg.snap->serverTime )
- {
- Com_Error( ERR_FATAL, "CG_ProcessSnapshots: Server time went backwards" );
- }
- }
-
- #ifdef _SNAPSHOT_EXTRAPOLATION
-
- if ( cg.needNextSnap )
- {
- if ( cg_drawSnapshot.integer )
- Com_Printf ( "NEED\n" );
-
- break;
- }
-
- #endif
-
- // if our time is < nextFrame's, we have a nice interpolating state
- if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime )
- {
- break;
- }
-
- #ifdef _SNAPSHOT_EXTRAPOLATION
-
- cg.needNextSnap = qtrue;
-
- if ( cg_drawSnapshot.integer )
- Com_Printf ( "SNAP: time=%i\n", cg.time );
-
- #else
- // we have passed the transition from nextFrame to frame
- CG_TransitionSnapshot();
- #endif
-
- } while ( 1 );
-
- // assert our valid conditions upon exiting
- if ( cg.snap == NULL )
- {
- Com_Error( ERR_FATAL, "CG_ProcessSnapshots: cg.snap == NULL" );
- }
-
- if ( cg.time < cg.snap->serverTime )
- {
- // this can happen right after a vid_restart
- cg.time = cg.snap->serverTime;
- }
-
- #ifndef _SNAPSHOT_EXTRAPOLATION
- if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time )
- {
- Com_Error( ERR_FATAL, "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" );
- }
- #endif
- }
-